

use std::process::Stdio;

use clap::Args;
use serde::{Deserialize, Serialize};

use wtask_base::{
    error::WResult,
    export::{AsyncReadExt, AsyncWriteExt, BufReader, MpscReceiver, MpscSender, TokioCommand, log_debug, mpsc_channel, select, tokio_spawn, yield_now},
    machine::get_home_dir, tcp::{WTcpStream, stream_mpscchannel_exchange_key, stream_read_raw}};

use crate::{basic::Empty, func::{stdin_read_line, write_log}, utils::{AppTrait, StreamStatus}};



#[derive(Debug, Clone, Serialize, Deserialize, Args)]
pub struct AppShell {
    /// 指定shell运行
    #[arg(long)]
    pub(super) shell: Option<String>
}


impl AppShell {
    async fn conn_shell(&self) -> WResult<(MpscSender<Vec<u8>>, MpscReceiver<Vec<u8>>)> {
        // let exe = if let Some(shell) = &self.shell {
        //     shell.to_owned()
        // } else {
        //     env::var("SHELL").unwrap_or("bash".to_owned())
        // };
        let mut child = TokioCommand::new("su");
        let dir = get_home_dir();

        // log
        let mut child = child
            .current_dir(&dir)
            .arg("-")
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .stdin(Stdio::piped())
            .env("PS1", "[\\u@wTask \\W]\\$ ")
            .spawn()?; // 启动子进程

        let (tx1, mut rx1) = mpsc_channel::<Vec<u8>>(128);
        let (tx2, rx2) = mpsc_channel::<Vec<u8>>(128);

        if let Some(mut stdin) = child.stdin.take() {
            tokio_spawn(async move {
                log_debug!("cmd server wait stdin read ...");
                while let Some(msg) = rx1.recv().await {
                    let a = stdin.write_all(&msg).await;
                    if a.is_err() {
                        log_debug!("stdin write error");
                        break;
                    }
                    yield_now().await;
                }
            });
        }
        
        if let Some(stdout) = child.stdout.take() {
            tokio_spawn(async move {
                log_debug!("shell server wait stdout read ...");
                let mut reader = BufReader::new(stdout);
                let mut buf = [0; 256];
                while let Ok(n) = reader.read(&mut buf).await {
                    if n == 0 {
                        break;
                    }
                    let data = buf[..n].to_vec();
                    log_debug!("stdout: {:?}", String::from_utf8_lossy(&data));
                    let a = tx2.send(data).await;
                    if a.is_err() {
                        log_debug!("stdout send error");
                        break;
                    }
                    yield_now().await;
                }
            });
        }

        Ok((tx1, rx2))
    }
}



impl AppTrait for AppShell {
    type Status = ();
    type Res = Empty;

    async fn client_handle<M: FnMut(bool, String) + Send + Sync>(self, stream: &WTcpStream, key: &str, _msg_func: M, _status: Option<Self::Status>) -> WResult<Empty> {
        let (stream_read, stream_write) = stream;
        let a = stdin_read_line(stream_write.as_ref(), key, "");
        let key_clone = key.to_string();
        let b = async move {
            loop {
                log_debug!("cmd client wait remote ...");
                let v = stream_read_raw(stream_read.as_ref(), &key_clone).await;
                if let Ok(data) = v {
                    write_log(data);
                } else {
                    log_debug!("cmd client read error: {:?}", v.err().unwrap());
                    break;
                }
            }
        };
        select! {
            _ = a => {},
            _ = b => {},
        }
        Ok(().into())
    }
    
    async fn server_handle(self, _server_token: &str, session_token: &str, stream_session: &WTcpStream, _stream_status: StreamStatus) -> WResult<()> {
        log_debug!("stream cmd get a session: {session_token}");
        let (tx, rx) = self.conn_shell().await?;
        log_debug!("stream cmd exchange data: {session_token}");
        let _ = stream_mpscchannel_exchange_key(stream_session, tx, rx, session_token).await;
        log_debug!("stream cmd end: {session_token}");
        Ok(())
    }
}

